1 module hip.api.data.commons;
2 
3 
4 ///Use @Asset instead of HipAsset.
5 struct HipAssetUDA(T)
6 {
7     string path;
8     static if(!(is(T == void)))
9         T function(string data) conversionFunction;
10     int start, end;
11 }
12 
13 /** 
14  * Params:
15  *   path = Path where the asset is located. 
16         It may receive an $ for path formatting with numbers(only valid when array is used.)
17  *   conversionFunc = A function with input the data located at "path", and return any data.
18  *   start = For Arrays. Inclusive. May be greater than end for reverse counting.
19  *   end = For Arrays. Inclusive.
20  * Returns: 
21  */
22 HipAssetUDA!T Asset(T)(string path, T function(string) conversionFunc, int start = 0, int end = 0){return HipAssetUDA!T(path, conversionFunc, start, end);}
23 /** 
24  * Params:
25  *   path = Path where the asset is located. 
26         It may receive an $ for path formatting with numbers(only valid when array is used.)
27  *   start = For Arrays. Inclusive. May be greater than end for reverse counting.
28  *   end = For Arrays. Inclusive.
29  * Returns: 
30  */
31 HipAssetUDA!void Asset(string path, int start = 0, int end = 0){return HipAssetUDA!void(path, start, end);}
32 
33 template FilterAsset(Attributes...)
34 {
35     import std.traits:isInstanceOf;
36     import std.meta:AliasSeq;
37     static foreach(attr; Attributes)
38         static if(isInstanceOf!(HipAssetUDA, typeof(attr)))
39         	alias FilterAsset = attr;
40 }
41 
42 template GetAssetUDA(Attributes...)
43 {
44     alias asset = FilterAsset!(Attributes);
45     static if(!is(typeof(asset) == void)) //Means it is a real struct.
46         enum GetAssetUDA = asset;
47     else
48         enum GetAssetUDA = HipAssetUDA!void();
49 }
50 
51 
52 public string[] splitLines(string input)
53 {
54     string[] ret;
55     size_t lastCut = 0;
56     foreach(i, ch; input)
57     {
58         if(ch == '\n')
59         {
60             ret~= input[lastCut..i];
61             lastCut = i+1;
62         }
63     }
64     if(lastCut < input.length) ret~= input[lastCut..$];
65     return ret;
66 }
67 
68 
69 string[] getModulesFromRoot(string modules, string root)
70 {
71     string[] ret = splitLines(modules);
72 
73     ptrdiff_t rootStart = -1;
74     foreach(i, mod; ret)
75     {
76         if(mod.length < root.length)
77         {
78             if(rootStart == -1)
79                 continue;
80             else
81                 return ret[rootStart..i];
82 
83         }
84         if(mod[0..root.length] == root)
85         {
86             if(rootStart == -1)
87                 rootStart = i;
88         }
89         else if(rootStart != -1)
90             return ret[rootStart..i];
91     }
92     assert(rootStart != -1, "Unable to find root "~root~" in modules list.");
93     return ret[rootStart..$];
94 }
95 
96 IHipAssetLoadTask loadAsset(type)(string assetPath)
97 {
98     import hip.api;
99     static if(is(type == IHipCSV))
100         return HipAssetManager.loadCSV(assetPath);
101     else static if(is(type == IHipFont))
102         return HipAssetManager.loadFont(assetPath);
103     else static if(is(type == IImage))
104         return HipAssetManager.loadImage(assetPath);
105     else static if(is(type == IHipIniFile))
106         return HipAssetManager.loadINI(assetPath);
107     else static if(is(type == IHipJSONC))
108         return HipAssetManager.loadJSONC(assetPath);
109     else static if(is(type == IHipTexture))
110         return HipAssetManager.loadTexture(assetPath);
111     else static if(is(type == IHipTextureAtlas))
112         return HipAssetManager.loadTextureAtlas(assetPath);
113     else static if(is(type == IHipTilemap))
114         return HipAssetManager.loadTilemap(assetPath);
115     else static if(is(type == IHipTileset))
116         return HipAssetManager.loadTileset(assetPath);
117     else static if(is(type == IHipAudioClip))
118         return HipAssetManager.loadAudio(assetPath);
119     else
120         return HipAssetManager.loadFile(assetPath);
121 }
122 IHipAssetLoadTask[] loadAsset(type)(string assetPath, int start, int end)
123 {
124     int sign = end - start >= 0 ? 1 : -1;
125     ///Include 1 for the upper bounds 
126     int count = ((end - start) * sign) + 1;
127     if(count == 1) return [loadAsset!type(assetPath)];
128     IHipAssetLoadTask[] ret = new IHipAssetLoadTask[count];
129 
130     static string formatStr(string str, int number)
131     {
132         import hip.util.to_string_range;
133         char[32] numSink = 0xff;
134         toStringRange(numSink[], number);
135         int charCount = 0;
136         while(numSink[charCount++] != 0xff){} charCount--;
137         //-1 for the $
138         char[] formattedStr = new char[(cast(int)str.length)-1+charCount];
139         int i = 0;
140         foreach(ch; str)
141         {
142             if(ch == '$')
143                 formattedStr[i..i+=charCount] = numSink[0..charCount];
144             else
145                 formattedStr[i++] = ch;
146         }
147         return formattedStr;
148     }
149 
150     foreach(i; 0..count) 
151         ret[i] = loadAsset!type(formatStr(assetPath, start+i*sign));
152     return ret;
153 }
154 
155 mixin template LoadAllAssets(string modules)
156 {
157     import hip.api.data.commons;
158     import std.file;
159     mixin LoadReferencedAssets!(splitLines(modules));
160 }
161 mixin template LoadReferencedAssets(string[] modules)
162 {
163     void loadReferenced()
164     {
165         static foreach(modStr; modules)
166         {{
167             mixin("import ",modStr,";");
168             alias theModule = mixin(modStr);
169             static foreach(moduleMemberStr; __traits(allMembers, theModule))
170             {{
171                 alias moduleMember = __traits(getMember, theModule, moduleMemberStr);
172                 static if(!is(moduleMember == module) && is(moduleMember type))
173                 {
174                     static if(is(type == class) || is(type == struct))
175                     {
176                         static foreach(classMemberStr; __traits(derivedMembers, type))
177                         {{
178                             alias classMember = __traits(getMember, type, classMemberStr);
179                             alias assetUDA = GetAssetUDA!(__traits(getAttributes, classMember));
180                             // pragma(msg, assetUDA);
181                             static if(assetUDA.path !is null)
182                             {{
183                                 import std.traits:isArray;
184                                 static if(isArray!(typeof(classMember))) alias memberType = typeof(classMember.init[0]);
185                                 else alias memberType = typeof(classMember);
186 
187                                 IHipAssetLoadTask[] tasks = hip.api.data.commons.loadAsset!(memberType)(assetUDA.path, assetUDA.start, assetUDA.end);
188                                 static if(!__traits(compiles, classMember.offsetof)) //Static 
189                                 {
190                                     
191                                     void loadTaskInto(IHipAssetLoadTask task, ref memberType member)
192                                     {
193                                         static if(__traits(hasMember, assetUDA, "conversionFunction"))
194                                             task.into(assetUDA.conversionFunction, &member);
195                                         else static if(is(memberType == string))
196                                             task.into(&member);
197                                         else
198                                             task.into!(memberType)(&member);
199                                     }
200                                     static if(isArray!(typeof(classMember)))
201                                     {
202                                         size_t start = classMember.length;
203                                         classMember.length+= tasks.length;
204                                         foreach(i, task; tasks)
205                                             loadTaskInto(task, classMember[start+i]);
206                                     }
207                                     else
208                                         loadTaskInto(tasks[0], classMember);
209                                 }
210                             }}
211                         }}
212                     }
213                 }
214             }}
215         }}
216     }
217 }
218 
219 
220 ///foreachAsset: void foreachAsset(T)(string assetPath)
221 mixin template ForeachAssetInClass(T, alias foreachAsset)
222 {
223     void ForeachAssetInClass()
224     {
225         import std.traits:isFunction;
226         static foreach(member; __traits(derivedMembers, T))
227         {{
228             alias theMember = __traits(getMember, T, member);
229             static if(!isFunction!theMember)
230             {
231                 alias type = typeof(theMember);
232                 enum assetUDA = GetAssetUDA!(__traits(getAttributes, theMember));
233                 static if(assetUDA.path != null)
234                     foreachAsset!(type, theMember)(assetUDA.path);
235             }
236         }}
237     }
238 }
239 
240 mixin template PreloadAssets()
241 {
242     private void _load(type, alias theMember)(string assetPath)
243     {
244         loadAsset!type(assetPath).into(&theMember);
245     }
246     alias preload = ForeachAssetInClass!(typeof(this), _load);
247 }
248 
249 /**
250 *   Usage:
251 ```d
252 class SomeScene : IHipPreloadable
253 {
254     mixin Preload; ///IHipPreloadable lets you use Preload symbol. 
255 
256     ///Will load "someTexture.png" inside the member 'texture'
257     @Asset("someTexture.png")
258     IHipTexture texture;
259 
260     ///Loads game levels inside this variable
261     @Asset("gameLevels.txt", &parseGameLevels)
262     GameLevel[] gameLevels
263 
264     ///Doesn't need to call 'preload()' to populate. As it is variable, it will be populated right after its load.
265     @Asset("helpText.txt")
266     static string helpText;
267 
268     void initialize()
269     {
270         preload(); ///Needed to call for populating your assets after the class creation
271     }
272 
273     GameLevel[] parseGameLevels(string data){return [];}
274 }
275 ```
276 */
277 interface IHipPreloadable
278 {
279     void preload();
280     string[] getAssetsForPreload();
281 
282     mixin template Preload()
283     {
284         mixin template finalImpl()
285         {
286             private __gshared string[] _assetsForPreload;
287             private __gshared void getAsset(T, alias member)(string asset){_assetsForPreload~= asset;}
288             private final void loadAsset(T, alias member)(string asset)
289             {
290                 alias mem = member;
291                 ///Take members that aren't static and populate them after loading.
292                 static if(__traits(compiles, mem.offsetof))
293                 {
294                     ///Try converting the member with conversion function
295                     static if(!__traits(compiles, HipAssetManager.get!T))
296                     {
297                         alias assetUDA = GetAssetUDA!(__traits(getAttributes, mem));
298                         static assert(__traits(hasMember, assetUDA, "conversionFunction"), 
299                         "Type has no conversion function and HipAssetManager can't infer its type.");
300                         mem = assetUDA.conversionFunction(HipAssetManager.get!string(asset));
301                     }
302                     else //Just get from asset manager
303                         mem = HipAssetManager.get!T(asset);
304                 }
305             }
306         }
307         mixin template impl()
308         {
309             string[] getAssetsForPreload()
310             {
311                 if(_assetsForPreload.length == 0)
312                 {
313                     mixin ForeachAssetInClass!(typeof(this), __traits(child, this, getAsset)) f;
314                     f.ForeachAssetInClass;
315                 }
316                 return _assetsForPreload;
317             }
318             void preload()
319             {
320                 mixin ForeachAssetInClass!(typeof(this), loadAsset) f;
321                 f.ForeachAssetInClass;
322             }
323         }
324         
325 
326         ///Deal with override/no override
327         mixin finalImpl;
328         static if(__traits(compiles, typeof(super).preload)){override: mixin impl;}
329         else{mixin impl;}
330     }
331 }
332 
333 
334 interface ILoadable
335 {
336     /** Should return if the asset is ready for use*/
337     bool isReady();
338 }
339 
340 /**
341 *   OpenGL Renderer must implement IReloadable for when changing device orientation.
342 */
343 interface IReloadable
344 {
345     bool reload();
346 }
347 
348 interface IHipAsset
349 {
350     string name() const;
351     string name(string newName);
352 
353     uint assetID() const;
354     uint typeID() const;
355 }
356 
357 
358 enum HipAssetResult
359 {
360     cantLoad,
361     loading,
362     loaded
363 }
364 
365 /** 
366  *  IHipAssetLoadTask is the base return type from any asset you want to `HipAssetManager.load{X}`.
367  *  The loading, unless otherwise stated, is asynchronous. For simple games, most of the time you won't need
368  *  to directly used LoadTask as currently the engine loads all the assets at startup to make it easier
369  *  to prototype a game without needing to think about those tasks.
370  *
371  *  `await` is not supported on WebAssembly export, so, don't use it if you plan to export to web.
372  */
373 interface IHipAssetLoadTask
374 {
375     HipAssetResult result() const;
376     ///Sets the result. Should not exist in user code.
377     HipAssetResult result(HipAssetResult result);
378 
379     IHipAsset asset();
380     ///Sets the asset. Should not exist in user code.
381     IHipAsset asset(IHipAsset asset);
382 
383     bool hasFinishedLoading() const;
384     ///Awaits the asset load process. Can't be used on WebAssembly export
385     void await();
386     ///When the variables finish loading, it will also assign the asset to the variables 
387     void into(void* function(IHipAsset asset) castFunc, IHipAsset*[] variables...);
388     final void into(T)(T*[] variables...){into((IHipAsset asset) => (cast(void*)cast(T)asset), cast(IHipAsset*[])variables);}
389     void into(string*[] variables...);
390 
391     ///May be executed instantly if the asset is already loaded.
392     void addOnCompleteHandler(void delegate(IHipAsset) onComplete);
393     void addOnCompleteHandler(void delegate(string) onComplete);
394     final void into(T)(T function(string) convertFunction, T*[] variables...)
395     {
396         T*[] vars = variables.dup;
397         addOnCompleteHandler((string data)
398         {
399             foreach(v; vars)
400                 *v = convertFunction(data);
401         });
402     }
403 
404 
405     /**
406     *   Awaits the asset to be loaded and if the load was possible, cast it to the type, else returns null.
407     *   Unsupported at WebAssembly.
408     */
409     T awaitAs(T)()
410     {
411         await();
412         //Ignore dynamic cast (future only) return cast(T)(cast(void*)asset);
413         if(hasFinishedLoading() && result == HipAssetResult.loaded)
414             return cast(T)asset;
415         return null;
416     }
417 }
418 
419 
420 ///Maybe will be deprecated in future. This is common in web, but it is a pain to work with.
421 interface IHipDeferrableTexture
422 {
423     void setTexture(IHipAssetLoadTask task);
424 }
425 interface IHipDeferrableText
426 {
427     void setFont(IHipAssetLoadTask task);
428 }
429 
430 interface IHipDeserializable
431 {
432     IHipDeserializable deserialize(string data);
433     IHipDeserializable deserialize(void* data);
434 }